home *** CD-ROM | disk | FTP | other *** search
/ The World of Computer Software / The World of Computer Software.iso / ccr.zip / CCR-NPC.T < prev    next >
Text File  |  1993-01-23  |  26KB  |  1,060 lines

  1. /*
  2.  * Colossal Cave Revisited
  3.  *
  4.  * A remake of Willie Crowther and Don Woods' classic Adventure.
  5.  * Converted from Donald Ekman's PC port of the original FORTRAN source.
  6.  * TADS version by David M. Baggett for ADVENTIONS.
  7.  *
  8.  * Please document all changes in the history so we know who did what.
  9.  *
  10.  * This source code is copylefted under the terms of the GNU Public
  11.  * License.  Essentially, this means that you are free to do whatever
  12.  * you wish with this source code, provided you do not charge any
  13.  * money for it or for any derivative works.
  14.  *
  15.  * ADVENTIONS distributes this game, but you are free to do what you will
  16.  * with it, provided you adhere to the terms in the GNU Public License.
  17.  * Send correspondence regarding this game or original works distributed
  18.  * by ADVENTIONS to 
  19.  *
  20.  *    ADVENTIONS
  21.  *    PO Box 851
  22.  *    Columbia, MD 21044
  23.  *
  24.  * If you would like a catalog of releases, please enclose a SASE.  Thanks!
  25.  *
  26.  * Contributors
  27.  *
  28.  *    dmb    In real life:    David M. Baggett
  29.  *        Internet:    <dmb@ai.mit.edu>
  30.  *        Compu$erve:    76440,2671 (ADVENTIONS account)
  31.  *        GEnie:        ADVENTIONS
  32.  *
  33.  * Modification History
  34.  *
  35.  * 1-Jan-93    dmb    rec.arts.int-fiction BETA release (source only)
  36.  *                      For beta testing only -- not for general
  37.  *            distribution.
  38.  *
  39.  */
  40.  
  41. /*
  42.  * This file handles non-player character (dwarves and pirate) movement.
  43.  *
  44.  * Be sure to update exitlist and/or npclist if you add any new travel
  45.  * verbs to CCR_Room.
  46.  */
  47. initNPC: function
  48. {
  49.     local    o;
  50.  
  51.     //
  52.     // Construct list of NPC exits for each room
  53.     //
  54.     o := firstobj(CCR_room);
  55.     while (o <> nil) {
  56.         if (not o.noNPCs) {
  57.             //
  58.             // Add this room to the global list of rooms
  59.             // the NPC's can be in.
  60.             //
  61.             global.NPCrooms := global.NPCrooms + o;
  62.  
  63.             do_exitlist(o);
  64.             do_npclist(o);
  65.         }
  66.         else if (global.debug) {
  67.             //
  68.             // Debugging info:
  69.             // 
  70.             "\b\"<< o.sdesc >>\" is off limits to NPC's."; 
  71.         }
  72.  
  73.         o := nextobj(o, CCR_room);
  74.     }
  75. }
  76.  
  77. /*
  78.  * Add standard exits to the list of exits that NPC's should check
  79.  * when they're wandering about randomly.
  80.  */
  81. do_exitlist: function(o)
  82. {
  83.     local    exitlist, i, j, gotit;
  84.  
  85.     //
  86.     // List of all exit property names that NPC's will consider.
  87.     // Note that magic words are left out because NPC's don't
  88.     // know them.
  89.     //
  90.     exitlist := [
  91.         &north &south &east &west
  92.         &ne &nw &se &sw
  93.         &up &down &in &out
  94.  
  95.         &jump &upstream &downstream &forwards &outdoors 
  96.         &left &right &cross &over &across &road &forest 
  97.         &valley &stairs &building &gully &stream &rock &bed 
  98.         &crawl &cobble &tosurface &dark &passage &low &canyon 
  99.         &awkward &giant &view &pit &crack &steps &dome &hall 
  100.         &barren &debris &hole &wall &broken &floor &toroom 
  101.         &slit &slab &depression &entrance &secret &cave 
  102.         &bedquilt &oriental &cavern &shell &reservoir &main 
  103.         &office &fork
  104.     ];
  105.  
  106.     for (i := 1; i <= length(exitlist); i++) {
  107.         //
  108.         // If this exit property is a simple
  109.         // object (prop 2), NPC's can use it, so
  110.         // add it to the room's NPC exit list.
  111.         //
  112.         // Make sure we don't add the same
  113.         // desination room twice.  Just because
  114.         // there are multiple travel verbs from
  115.         // one place to another doesn't mean the
  116.         // destination should be more likely.
  117.         //
  118.         if (proptype(o, exitlist[i]) = 2
  119.                     and not o.(exitlist[i]).noNPCs) {
  120.  
  121.             //
  122.             // Search current exitlist for
  123.             // this exit's destination.
  124.             //
  125.             gotit := nil;
  126.             for (j := 1; j <= length(o.NPCexits); j++) {
  127.                 if (o.(o.NPCexits[j]) = o.(exitlist[i])) {
  128.                     gotit := true;
  129.                     break;
  130.                 }
  131.             }
  132.             if (not gotit)
  133.                 o.NPCexits := o.NPCexits + exitlist[i];
  134.         }
  135.     }
  136. }
  137.  
  138. /*
  139.  * Add NPC special exits to the list of exits that NPC's should check
  140.  * when they're wandering about randomly.
  141.  */
  142. do_npclist: function(o)
  143. {
  144.     local    npclist, i;
  145.  
  146.     //
  147.     // NPC exits.  These get considered if even if they're methods.
  148.     // The only way they won't be added to the list of exits to
  149.     // try is if they're = nil and not methods.
  150.     //
  151.     npclist := [
  152.         &NPCexit1 &NPCexit2 &NPCexit3 &NPCexit4
  153.         &NPCexit5 &NPCexit6 &NPCexit7 &NPCexit8
  154.     ];
  155.  
  156.     for (i := 1; i <= length(npclist); i++) {
  157.         //
  158.         // If this NPC exit property is anything but
  159.         // nil (i.e., just nil, and not a method
  160.         // that returns nil). then NPC's can use it.
  161.         // Methods that return nil are fine because
  162.         // they might be conditional on some game
  163.         // events, like the crystal bridge having
  164.         // been created, etc.
  165.         //
  166.         if (proptype(o, npclist[i]) <> 5) // not = nil
  167.             o.NPCexits := o.NPCexits + npclist[i];
  168.     }
  169. }
  170.  
  171. /*
  172.  * Make sure NPC room connections are sound.
  173.  */
  174. check_connections: function
  175. {
  176.     local    o;
  177.  
  178.     o := firstobj(CCR_room);
  179.     while (o <> nil) {
  180.         if (not o.noNPCs)
  181.             do_debug(o);
  182.  
  183.         o := nextobj(o, CCR_room);
  184.     }
  185. }
  186.     
  187. do_debug: function(o)
  188. {
  189.     local    i;
  190.  
  191.     if (length(o.NPCexits) = 0) {
  192.         P(); I();
  193.         "Oh dear, someone seems to have damaged one of my 
  194.         room connections.  The room \"";
  195.  
  196.         o.sdesc;
  197.  
  198.         "\" has no exits for NPC's to follow, but it's not 
  199.         listed as off limits to NPC's.  Please notify the 
  200.         cave management as soon as possible!";
  201.     }
  202.     else if (global.debug) {
  203.         //
  204.         // Debugging info:
  205.         //             
  206.         "\b\""; o.sdesc; "\" has "; say(length(o.NPCexits));
  207.         if (length(o.NPCexits) > 1)
  208.             " NPC exits:";
  209.         else
  210.             " NPC exit:";
  211.  
  212.         for (i := 1; i <= length(o.NPCexits); i++) {
  213.             "\b\t-> ";
  214.             if (o.(o.NPCexits[i]))
  215.                 o.(o.NPCexits[i]).sdesc;
  216.             else
  217.                 "(nil)";
  218.         }
  219.     }
  220. }
  221.  
  222. /*
  223.  * A class defining some common things for NPC's.
  224.  */
  225. class NPC: object
  226.     //
  227.     // List of currect pirate locations.
  228.     //
  229.     loclist = []        // where they are
  230.     newloclist = []        // where they're going
  231.  
  232.     //
  233.     // Scatter any NPC's that are currently in
  234.     // the room to random rooms in the cave.  (We
  235.      // have to make sure the new rooms aren't off
  236.     // limits to dwarves, though.) 
  237.     //
  238.     scatter = {
  239.         local    i, dest, len, r;
  240.  
  241.         self.newloclist := [];
  242.         for (i := 1; i <= length(self.loclist); i++) {
  243.             if (Me.isIn(self.loclist[i])) {
  244.                 //
  245.                 // Make sure we get a real location.
  246.                 //
  247.                 dest := nil;
  248.                 while (dest = nil) {
  249.                     len := length(global.NPCrooms);
  250.                     r := rand(len);
  251.                     dest := global.NPCrooms[r];
  252.                 }
  253.  
  254.                 self.newloclist += dest;
  255.             }
  256.             else {
  257.                 self.newloclist += self.loclist[i];
  258.             }
  259.  
  260.         }
  261.         self.loclist := self.newloclist;
  262.     }
  263.  
  264.     //
  265.     // Returns true if any NPC's of this type are in locations
  266.     // adjacent to the player.  (I.e., if any NPC's could take
  267.     // any exit that would bring them to the player's current
  268.     // location.)
  269.     //
  270.     anyadjacent = {
  271.         local    adjacent, i, j, len, dir, dest;
  272.  
  273.         adjacent := nil;
  274.         for (i := 1; i <= length(self.loclist); i++) {
  275.             len := length(self.loclist[i].NPCexits);
  276.             for (j := 1; j <= len; j++) {
  277.                 dir := self.loclist[i].NPCexits[j];
  278.                 dest := self.loclist[i].(dir);
  279.  
  280.                 //
  281.                 // We need to check the destination
  282.                 // to be sure it exists.  It may be
  283.                 // nil if we called an NPCexit method.
  284.                 //
  285.                 if (dest) if (Me.isIn(dest)) {
  286.                     adjacent := true;
  287.                     break;
  288.                 }
  289.             }
  290.  
  291.             //
  292.             // If we've found an adjacent pirate we
  293.             // can stop looking.
  294.             //
  295.             if (adjacent)
  296.                 break;
  297.         }
  298.  
  299.         return adjacent;
  300.     }
  301. ;
  302.  
  303. /*
  304.  * Move the dwarves.  See the global object in ccr-std.t for paramters.
  305.  */
  306. Dwarves: NPC, Actor
  307.     rhetoricalturn = -999    // hack -- see yesVerb in ccr-verbs.t
  308.     attackers = 0        // number of dwarves that attack this turn
  309.     
  310.     sdesc = "threatening little dwarf"
  311.     ldesc = {
  312.         "It's probably not a good idea to get too close.  Suffice
  313.         it to say the little guy's pretty aggressive.";
  314.     }
  315.  
  316.     noun = 'dwarf' 'dwarves' 'guy'
  317.     adjective = 'threatening' 'nasty' 'little' 'mean'
  318.  
  319.     //
  320.     // We don't use actorDesc for the dwarves because it gets printed
  321.     // too early.  (We want to let the player know that a dwarf is
  322.     // in the room as soon as the dwarf moves into the room, not at
  323.     // the start of the next turn.)
  324.     //
  325.     actorDesc = {}
  326.  
  327.     locationOK = true    // tell compiler OK for location to be method
  328.     location = {
  329.         local    i;
  330.  
  331.         //
  332.         // Check each dwarf's location.  If at least one dwarf
  333.         // is in the same location as the player, make our
  334.         // location the same as the player's.
  335.         //
  336.         for (i := 1; i <= length(self.loclist); i++)
  337.             if (Me.isIn(self.loclist[i]))
  338.                 return self.loclist[i];
  339.  
  340.         return nil;
  341.     }
  342.  
  343.     verDoKick(actor) =  {}
  344.     doKick(actor) = {
  345.         "You boot the dwarf across the room.  He curses, then 
  346.         gets up and brushes himself off.  Now he's madder 
  347.         than ever!";
  348.     }
  349.     verDoAttack(actor) = {
  350.         if (not axe.isIn(Me)) {
  351.             "With what?  Your bare hands?";
  352.             self.rhetoricalturn := global.turnsofar;
  353.         }
  354.     }
  355.     doAttack(actor) = { self.doAttackWith(actor, axe); }
  356.  
  357.     //
  358.     // The following method is called when the player responds, "yes"
  359.     // to "With what?  Your bare hands?"
  360.     //
  361.     nicetry = { "You wish."; }
  362.     
  363.     verDoAttackWith(actor, io) = {}
  364.     doAttackWith(actor, io) = {
  365.         //
  366.         // If the player throws the axe at the dwarf, he
  367.         // might reduce the dwarf to a cloud of greasy
  368.         // black smoke.
  369.         //
  370.         if (io = axe) {
  371.             if (rand(100) <= global.dwarfhit) {
  372.                 "You killed a little dwarf.  The body 
  373.                 vanishes in a cloud of greasy black 
  374.                 smoke.";
  375.  
  376.                 //
  377.                 // Remove this location from our list
  378.                 // of locations where dwarves are.
  379.                 //
  380.                 self.loclist -= self.location;
  381.             }
  382.             else {
  383.                 "You attack a little dwarf, but he 
  384.                 dodges out of the way.";
  385.             }
  386.  
  387.             axe.moveInto(Me.location);
  388.         }
  389.         else if (io = Hands)
  390.             self.nicetry;
  391.         else
  392.             "Somehow I doubt that'll be very effective.";
  393.     }
  394.     
  395.     verIoGiveTo(actor) = {}
  396.     ioGiveTo(actor, dobj) = {
  397.         if (dobj = tasty_food) {
  398.             self.doFeed(Me);
  399.         }
  400.         else {
  401.             "The dwarf is not at all interested in your 
  402.             offer.  (The reason being, perhaps, that if 
  403.             he kills you he gets everything you have 
  404.             anyway.)";
  405.         }
  406.     }
  407.  
  408.     verIoThrowAt(actor) = { self.verIoGiveTo(actor); }
  409.     ioThrowAt(actor, dobj) = {
  410.         if (dobj = axe)
  411.             self.doAttackWith(actor, dobj);
  412.         else
  413.             self.ioGiveTo(actor, dobj);
  414.     }
  415.     verIoThrowTo(actor) = { self.verIoGiveTo(actor); }
  416.     ioThrowTo(actor, dobj) = {
  417.         if (dobj = axe)
  418.             self.doAttackWith(actor, dobj);
  419.         else
  420.             self.ioGiveTo(actor, dobj);
  421.     }
  422.  
  423.     verDoFeed(actor) = {}
  424.     doFeed(actor) = {
  425.         if (tasty_food.isIn(Me)) {
  426.             "You fool, dwarves eat only coal!  Now you've 
  427.             made him *really* mad!!";
  428.         }
  429.         else {
  430.             "You have no food to give the dwarf.";
  431.         }
  432.     }
  433.  
  434.     //
  435.     // Place dwarves in starting locations.
  436.     //
  437.     place = {
  438.         local    i, loc, r;
  439.  
  440.         self.loclist := [];
  441.         for (i := 1; i <= global.dwarves; i++) {
  442.             //
  443.             // If there are any fixed starting locations
  444.             // for dwarves left, put this dwarf in the
  445.             // next one.  Otherwise place him randomly.
  446.             //
  447.             loc := nil;
  448.             if (length(global.dwarfloc) >= i)
  449.                 loc := global.dwarfloc[i];
  450.  
  451.             //
  452.             // Invalidate initial location if it's off limits
  453.             // to NPC's.
  454.             //
  455.             if (loc)
  456.                 if (loc.noNPCs)
  457.                     loc := nil;
  458.  
  459.             //
  460.             // Make sure we get a real location.
  461.             //
  462.             while (loc = nil) {
  463.                 r := rand(length(global.NPCrooms));
  464.                 loc := global.NPCrooms[r];
  465.             }
  466.             
  467.             //
  468.             // Add this dwarf's location to the list.
  469.             //
  470.             self.loclist := self.loclist + loc;
  471.         }
  472.     }
  473.  
  474.     //
  475.     // Move dwarves.
  476.     //
  477.     move = {
  478.         local    i, j, len, r, dest, done, dir, loc;
  479.  
  480.         //
  481.         // Move each remaining dwarf.
  482.         //
  483.         // If the dwarf is currently in the player's location,
  484.         // he stays where he is.
  485.         //
  486.         // If a dwarf is in a location adjacent to the player's
  487.         // current location, he moves into the player's location
  488.         // if he can.  (We check his possible exits to see if
  489.         // any of them go the player's location.)  A consequence
  490.         // of this is that dwarves will follow the player
  491.         // relentlessly once they've spotted him.  (But the global
  492.         // value dwarftenacity can be set to prevent dwarves
  493.         // from *always following*, of course.)
  494.         //
  495.         // If a dwarf isn't adjacent to the player, he just moves
  496.         // around randomly.
  497.         //
  498.         self.newloclist := [];
  499.         self.attackers := 0;    // assume no dwarves attack this turn
  500.         for (i := length(self.loclist); i > 0; i--) {
  501.             //
  502.             // Get a copy of this dwarf's location for speed.
  503.             //
  504.             loc := self.loclist[i];
  505.  
  506.             //
  507.             // Haven't found a new location yet.
  508.             //
  509.             done := nil;
  510.  
  511.             //
  512.             // In player's current location?
  513.             //
  514.             if (Me.isIn(loc)) {
  515.                 dest := loc;    // stay put
  516.                 done := true;
  517.             }
  518.  
  519.             //
  520.             // Try each exit and see if we can reach the
  521.             // player.  If we have an exit that leads to
  522.             // the player, we know it's an OK destination
  523.             // location, since we pruned off all the noNPCs
  524.             // rooms when we constructed the exit lists. 
  525.             //
  526.             len := length(loc.NPCexits);
  527.             if (not done) for (j := len; j > 0; j--) {
  528.                 dir := loc.NPCexits[j];
  529.                 dest := loc.(dir);
  530.  
  531.                 //
  532.                 // We need to check the destination
  533.                 // to be sure it exists.  It may be
  534.                 // nil if we called an NPCexit method.
  535.                 //
  536.                 if (dest) if (Me.isIn(dest)) {
  537.                     //
  538.                     // Is this dwarf tenacious enough
  539.                     // to follow the player?
  540.                     //
  541.                     if (rand(100) <= global.dtenacity)
  542.                         done := true;
  543.                     break;
  544.                 }
  545.             }
  546.  
  547.             //
  548.             // Have we found a destination yet?  If not,
  549.             // move dwarf to a randomly selected adjacent
  550.             // location.
  551.             //
  552.             // We need to check the destination because
  553.             // the NPCexit methods in the rooms can sometimes
  554.             // return nil.  (For example, when the crystal
  555.             // bridge doesn't exist yet, the giant's door
  556.             // has not been opened, etc.)
  557.             //
  558.             while (not done) {
  559.                 dir := loc.NPCexits[rand(len)];
  560.                 dest := loc.(dir);
  561.  
  562.                 if (dest)
  563.                     done := true;
  564.             }
  565.  
  566.             //
  567.             // Set new destination.
  568.             //
  569.             self.newloclist += dest; 
  570.  
  571.             //
  572.             // If the dwarf didn't move, he has an opportunity
  573.             // to attack.
  574.             //
  575.             if (loc = dest) {
  576.                 if (Me.isIn(dest))
  577.                     if (rand(100) <= global.dwarfattack)
  578.                         self.attackers++;
  579.  
  580.                 //
  581.                 // Print some debugging info if in debug mode
  582.                 //
  583.                 if (global.debug) {
  584.                     P();
  585.                     "Dwarf stays \""; dest.sdesc; ".\"\n";
  586.                 }
  587.             }
  588.             else {
  589.                 //
  590.                 // Print some debugging info if in debug mode
  591.                 //
  592.                 if (global.debug) {
  593.                     P();
  594.                     "Dwarf moves from \"";
  595.                     self.loclist[i].sdesc; "\" to \"";
  596.                     dest.sdesc; ".\"\n";
  597.                 }
  598.             }
  599.         }
  600.  
  601.         //
  602.         // Replace old locations with destinations.
  603.         //
  604.         self.loclist := self.newloclist;
  605.  
  606.         self.tell;
  607.     }
  608.  
  609.     //
  610.     // Tell the player what's going on with the dwarves.
  611.     //
  612.     tell = {    
  613.         local    i, j, len, r, dest, done, dir, count, adjacent;
  614.  
  615.         //
  616.         // Count how many dwarves are in the room with the player.
  617.         //
  618.         count := 0;
  619.         for (i := length(self.loclist); i > 0; i--)
  620.             if (Me.isIn(self.loclist[i]))
  621.                 count++;
  622.  
  623.         //
  624.         // If any dwarves are in the room with the player and
  625.         // the axe hasn't been thrown yet, throw the axe and
  626.         // scatter the dwarves.
  627.         //
  628.         if (count > 0 and axe.location = nil) {
  629.             P(); I();
  630.  
  631.             "A little dwarf just walked around a corner, 
  632.             saw you, threw a little axe at you which 
  633.             missed, cursed, and ran away.";
  634.  
  635.             axe.moveInto(self.location);
  636.  
  637.             //
  638.             // Scatter any dwarves in the room.
  639.             //
  640.             self.scatter;
  641.  
  642.             //
  643.             // No dwarves in the room.  Be sure we take back
  644.             // those attacks too...
  645.             //
  646.             count := 0;
  647.             self.attackers := 0;
  648.         }
  649.  
  650.         //
  651.         // Tell the player if any dwarves are in the room with him,
  652.         // or if any are nearby.
  653.         //
  654.         if (count = 0) {
  655.             //
  656.             // If no dwarves are in the room, but at least
  657.             // one dwarf is in an adjacent location, tell
  658.             // the player he hears something.
  659.             //
  660.             // (Only the pirate makes noise in the original,
  661.             // which seem a bit strange and not as much fun.)
  662.             //
  663.             if (self.anyadjacent) {
  664.                 P(); I(); "You hear the pitter-patter 
  665.                 of little feet.";
  666.             }
  667.         }
  668.         else if (count = 1) {
  669.             P(); I();
  670.             "There is a threatening little dwarf in the 
  671.             room with you.";
  672.         }
  673.         else if (count > 1) {
  674.             P(); I();
  675.             "There are "; say(count); " threatening 
  676.             little dwarves in the room with you.";
  677.         }
  678.  
  679.         //
  680.         // Handle dwarf attacks.
  681.         //
  682.         if (self.attackers > 0) {
  683.             if (self.attackers = 1) {
  684.                 if (count = 1)
  685.                     " He throws a knife at you!";
  686.                  else
  687.                     " One of them throws a knife 
  688.                     at you!";
  689.             }
  690.             else {
  691.                 if (self.attackers = count) {
  692.                     if (count = 2)
  693.                         " Both of them throw 
  694.                         knives at you!";
  695.                     else 
  696.                         " All of them throw 
  697.                         knives at you!";
  698.                 }
  699.                 else {
  700.                     say(self.attackers); " of them throw 
  701.                     knives at you!";
  702.                 }
  703.             }
  704.  
  705.             //
  706.             // Curtains for our hero?!
  707.             //
  708.             count := 0;
  709.             for (i := 1; i <= self.attackers; i++) {
  710.                 if (rand(100) <= global.dwarfaccuracy)
  711.                     count++;
  712.             }
  713.  
  714.             P(); I();
  715.             if (count > 0) {
  716.                 if (count = self.attackers) {
  717.                     if (count = 1)
  718.                         "It gets you!";
  719.                     else if (count = 2)
  720.                         "Both of them get you!";
  721.                     else
  722.                         "All of them get you!";
  723.                 }
  724.                 else if (count = 1) {
  725.                     "One of them gets you!";
  726.                 }
  727.                 else {
  728.                     say(count); " of them get 
  729.                     you!";
  730.                 }
  731.  
  732.                 die();
  733.             }
  734.             else {
  735.                 if (self.attackers = 1) 
  736.                     "It misses you!";
  737.                 else if (self.attackers = 2)
  738.                     "Both of them miss you!";
  739.                 else
  740.                     "They all miss you!";
  741.             }
  742.         }
  743.     }
  744. ;
  745. /*
  746.  * The player can never get the dwarves' knives (that would be too easy),
  747.  * but we'll let him examine them anyway.
  748.  */
  749. DwarfKnives: decoration
  750.     sdesc = "dwarf's knife"
  751.     ldesc = { self.verifyRemove(Me); }
  752.     noun = 'knife' 'knives'
  753.     adjective = 'sharp' 'nasty' 'dwarf\'s' 'dwarvish' 'dwarven'
  754.             'dwarfish'
  755.  
  756.     locationOK = true    // tell compiler OK for location to be method
  757.     location = {
  758.         return Dwarves.location;
  759.     }
  760.  
  761.     verifyRemove(actor) = {
  762.         "The dwarves' knives vanish as they strike the walls 
  763.         of the cave.";
  764.     }
  765.  
  766.     verIoAttackWith(actor) = {
  767.         "You don't have the dwarf's knife!";
  768.     }
  769. ;
  770.  
  771. /*
  772.  * Move the pirate(s).  See the global object in ccr-std.t for paramters.
  773.  *
  774.  * This code is quite similar to the Dwarves code, but is simple because
  775.  * there's never any interaction between the player and the pirates. (The
  776.  * pirates just come in, do their stuff, and vanish.)
  777.  *
  778.  * Note that even if there's more than one pirate, the text printed in
  779.  * in this object will treat all the pirates as a single one. So the
  780.  * only difference having multiple pirates makes is that the more you
  781.  * have, the more likely the player is to run into "him."
  782.  */
  783. Pirates: NPC, Actor
  784.     location = nil    // not a real actor, so pretend we don't exist
  785.  
  786.     seen = nil    // has the player seen (one of) us?
  787.  
  788.     //
  789.     // Place pirates in starting locations.
  790.     //
  791.     place = {
  792.         local    i, loc, r;
  793.  
  794.         self.loclist := [];
  795.         for (i := 1; i <= global.pirates; i++) {
  796.             //
  797.             // If there are any fixed starting locations
  798.             // for pirates left, put this pirate in the
  799.             // next one.  Otherwise place him randomly.
  800.             //
  801.             loc := nil;
  802.             if (length(global.pirateloc) >= i)
  803.                 loc := global.pirateloc[i];
  804.  
  805.             //
  806.             // Invalidate initial location if it's off limits
  807.             // to NPC's.
  808.             //
  809.             if (loc)
  810.                 if (loc.noNPCs)
  811.                     loc := nil;
  812.  
  813.             //
  814.             // Make sure we get a real location.
  815.             //
  816.             while (loc = nil) {
  817.                 r := rand(length(global.NPCrooms));
  818.                 loc := global.NPCrooms[r];
  819.             }
  820.             
  821.             //
  822.             // Add this pirate's location to the list.
  823.             //
  824.             self.loclist := self.loclist + loc;
  825.         }
  826.     }
  827.  
  828.     //
  829.     // Move pirates.
  830.     //
  831.     move = {
  832.         local    i, j, len, r, dest, done, dir;
  833.  
  834.         //
  835.         // Move each remaining pirate.
  836.         //
  837.         // If the pirate is currently in the player's location,
  838.         // he stays where he is.
  839.         //
  840.         // If a pirate is in a location adjacent to the player's
  841.         // current location, he moves into the player's location
  842.         // if he can.  We limit this with the ptenacity global.
  843.         //
  844.         // If a pirate isn't adjacent to the player, he just moves
  845.         // around randomly.
  846.         //
  847.         self.newloclist := [];
  848.         for (i := 1; i <= length(self.loclist); i++) {
  849.             //
  850.             // Haven't found a new location yet.
  851.             //
  852.             done := nil;
  853.  
  854.             //
  855.             // In player's current location?
  856.             //
  857.             if (Me.isIn(self.loclist[i])) {
  858.                 dest := self.loclist[i];    // stay put
  859.                 done := true;
  860.             }
  861.  
  862.             //
  863.             // Try each exit and see if we can reach the
  864.             // player.  If we have an exit that leads to
  865.             // the player, we know it's an OK destination
  866.             // location, since we pruned off all the noNPCs
  867.             // rooms when we constructed the exit lists. 
  868.             //
  869.             len := length(self.loclist[i].NPCexits);
  870.             if (not done) for (j := 1; j <= len; j++) {
  871.                 dir := self.loclist[i].NPCexits[j];
  872.                 dest := self.loclist[i].(dir);
  873.  
  874.                 //
  875.                 // We need to check the destination
  876.                 // to be sure it exists.  It may be
  877.                 // nil if we called an NPCexit method.
  878.                 //
  879.                 if (dest) if (Me.isIn(dest)) {
  880.                     //
  881.                     // Is this pirate tenacious enough
  882.                     // to follow the player?
  883.                     //
  884.                     if (rand(100) <= global.ptenacity)
  885.                         done := true;
  886.                     break;
  887.                 }
  888.             }
  889.  
  890.             //
  891.             // Have we found a destination yet?  If not,
  892.             // move pirate to a randomly selected adjacent
  893.             // location.
  894.             //
  895.             // We need to check the destination because
  896.             // the NPCexit methods in the rooms can sometimes
  897.             // return nil.  (For example, when the crystal
  898.             // bridge doesn't exist yet, the giant's door
  899.             // has not been opened, etc.)
  900.             //
  901.             while (not done) {
  902.                 dir := self.loclist[i].NPCexits[rand(len)];
  903.                 dest := self.loclist[i].(dir);
  904.  
  905.                 if (dest)
  906.                     done := true;
  907.             }
  908.  
  909.             //
  910.             // Set new destination.
  911.             //
  912.             self.newloclist += dest; 
  913.  
  914.             //
  915.             // Print some debugging info if in debug mode
  916.             //
  917.             if (self.loclist[i] = dest) {
  918.                 if (global.debug) {
  919.                     P();
  920.                     "Pirate stays \""; dest.sdesc; ".\"\n";
  921.                 }
  922.             }
  923.             else {
  924.                 if (global.debug) {
  925.                     P();
  926.                     "Pirate moves from \"";
  927.                     self.loclist[i].sdesc; "\" to \"";
  928.                     dest.sdesc; ".\"\n";
  929.                 }
  930.             }
  931.         }
  932.  
  933.         //
  934.         // Replace old locations with destinations.
  935.         //
  936.         self.loclist := self.newloclist;
  937.  
  938.         self.tell;
  939.     }
  940.  
  941.     //
  942.     // Tell the player what's going on with the pirates.
  943.     //
  944.     tell = {    
  945.         local    i, t, count, snagged;
  946.  
  947.         //
  948.         // Count how many pirates are in the room with the player.
  949.         // (We really only need to know if there are any at all,
  950.         // but this is just as easy.)
  951.         //
  952.         count := 0;
  953.         for (i := 1; i <= length(self.loclist); i++)
  954.             if (Me.isIn(self.loclist[i]))
  955.                 count++;
  956.                         
  957.         //
  958.         // Tell the player if any pirates are nearby.
  959.         //
  960.         if (count = 0) {
  961.             //
  962.             // If no pirates are in the room, but at least
  963.             // one pirate is in an adjacent location, tell
  964.             // the player he hears something.
  965.             //
  966.             if (self.anyadjacent) {
  967.                 P(); I();
  968.                 "There are faint rustling noises from 
  969.                 the darkness behind you.";
  970.             }
  971.         }
  972.         else if (count > 0) {
  973.             //
  974.             // A pirate has snagged the player.
  975.             // Move any treasures the player is carring
  976.             // to the pirate's repository, currently
  977.             // hard-coded as Dead_End_13 because there's
  978.             // code in that room that can't easily be
  979.             // made general.
  980.             //
  981.             // Since the player may be keeping his treasures
  982.             // in containers, it's actually easier just
  983.             // to search through the global list of
  984.             // treasures and check each one's location to
  985.             // see if it's in the player, rather than
  986.             // recursively checking all the player's belongings
  987.             // and seeing if they're treasures.  (Also, we want
  988.             // to get treasures that are just lying around in
  989.             // the room too.)
  990.             //
  991.             snagged := 0;
  992.             for (i := 1; i <= length(global.treasurelist); i++) {
  993.                 t := global.treasurelist[i] ;
  994.                 if (t.isIn(Me.location)) {
  995.                     t.moveInto(Dead_End_13);
  996.                     snagged++;
  997.                 }
  998.             }
  999.  
  1000.             //
  1001.             // Print a message telling the player what happened.
  1002.             //
  1003.             if (snagged > 0) {
  1004.                 P();
  1005.                 I(); "Out from the shadows behind you 
  1006.                 pounces a bearded pirate!  \"Har, 
  1007.                 har,\" he chortles.  \"I'll just take 
  1008.                 all this booty and hide it away with 
  1009.                 me chest deep in the maze!\"  He 
  1010.                 snatches your treasure and vanishes 
  1011.                 into the gloom.";
  1012.             }
  1013.             else {
  1014.                 //
  1015.                 // In the original code, if you weren't
  1016.                 // holding the lamp, you just wouldn't
  1017.                 // see the pirate when you weren't
  1018.                 // carrying any treasures.  This seems
  1019.                 // bogus, so I've added a conditional here.
  1020.                 //
  1021.                 P();
  1022.                 I(); "There are faint rustling noises 
  1023.                 from the darkness behind you.  As you 
  1024.                 turn toward them, ";
  1025.  
  1026.                 if (brass_lantern.isIn(Me))
  1027.                     "the beam of your lamp falls 
  1028.                     across";
  1029.                 else
  1030.                     "you spot";
  1031.  
  1032.                 " a bearded pirate. He is carrying a 
  1033.                 large chest.  \"Shiver me timbers!\" 
  1034.                 He cries, \"I've been spotted!  I'd 
  1035.                 best hie meself off to the maze to 
  1036.                 hide me chest!\" With that, he 
  1037.                 vanishes into the gloom.";
  1038.             }
  1039.  
  1040.             //
  1041.             // Install the treasure chest if it hasn't
  1042.             // already been installed.  No worries about
  1043.             // the chest appearing out of nowhere when
  1044.             // the player's at Dead_End_13, because the
  1045.             // pirate can't go there.  (It's off limits
  1046.             // to NPC's.)
  1047.             //
  1048.             if (not self.seen) {
  1049.                 treasure_chest.moveInto(Dead_End_13);
  1050.                 self.seen := true;
  1051.             }
  1052.  
  1053.             //
  1054.             // Scatter any pirates in the room.
  1055.             //
  1056.             self.scatter;
  1057.         }
  1058.     }
  1059. ;
  1060.